动态类型
C# 4引入了dynamic关键字,以用于定义变量。例如:
dynamic myDynamicVar;
与前面介绍的var关键字不同,的确存在动态类型,所以在声明myDynamicVar时,不必初始化它的值。
动态类型不同寻常之处是,它仅在编译期间存在,在运行期间它会被System.Object类型替代。这是较细微的实现细节,但必须记住这一点,因为这可能澄清了后面的一些讨论。
一旦有了动态变量,就可以继续访问其成员(这里没有列出实际获取变量值的代码)。
myDynamicVar.DoSomething("With this!");
无论myDynamicVar实际包含什么值,这行代码都会编译。但是,如果所请求的成员不存在,在执行这行代码时会生成一个RuntimeBinderException类型的异常。
实际上,像这样的代码提供了一个应在运行期间应用的“处方”。检查myDynamicVar的值,找到带一个字符串参数的DoSomething()方法,并在需要时调用它。
这最好举例说明。
警告:下面的示例仅用于演示!一般情况下,应仅在动态类型是唯一的选项时使用它们,例如处理非.NET对象。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CSharp.RuntimeBinder;
namespace Ch14Ex03
{
class MyClass1
{
public int Add(int var1, int var2)
{
return var1 + var2;
}
}
class MyClass2
{
}
class Program
{
static int callCount = 0;
static dynamic GetValue()
{
if(callCount++ == 0)
{
return new MyClass1();
}
return new MyClass2();
}
static void Main(string[] args)
{
try
{
dynamic firstResult = GetValue();
dynamic secondResult = GetValue();
Console.WriteLine("firstResult is: {0}",
firstResult.ToString());
Console.WriteLine("secondResult is: {0}",
secondResult.ToString());
Console.WriteLine("firstResult call: {0}",
firstResult.Add(2, 3));
Console.WriteLine("secondResult call: {0}",
secondResult.Add(2, 3));
}
catch(RuntimeBinderException ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
}
示例的说明
这个示例使用一个方法返回两个类型的对象中的一个,以获取动态值,再尝试使用所获取的对象。代码在编译时没有遇到任何问题,但尝试访问不存在的方法时,抛出(并处理)了一个异常。
首先,为包含RuntimeBindingException异常的名称空间添加一条using语句:
using Microsoft.CSharp.RuntimeBinder;
接着定义两个类MyClass1和MyClass2,其中MyClass1包含Add()方法,而MyClass2不包含成员:
class MyClass1 { public int Add(int var1, int var2) { return var1 + var2; } } class MyClass2 { }
还要给Program类添加一个字段(callCount)和一个方法(GetValue()),以获取其中一个类的实例:
static int callCount = 0; static dynamic GetValue() { if(callCount++ == 0) { return new MyClass1(); } return new MyClass2(); }
使用一个简单的调用计数器,这样,第一次调用这个方法时,返回MyClass1的一个实例,之后返回MyClass2的实例。注意dynamic关键字可以用作方法的返回类型。
接着,Main()中的代码调用GetValue()方法两次,再尝试在返回的两个值上依次调用GetString()和Add()。这些代码放在try...catch块中,以捕获可能发生的RuntimeBinderException类型的异常。
static void Main(string[] args) { try { dynamic firstResult = GetValue(); dynamic secondResult = GetValue(); Console.WriteLine("firstResult is: {0}", firstResult.ToString()); Console.WriteLine("secondResult is: {0}", secondResult.ToString()); Console.WriteLine("firstResult call: {0}", firstResult.Add(2, 3)); Console.WriteLine("secondResult call: {0}", secondResult.Add(2, 3)); } catch(RuntimeBinderException ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); }
可以肯定,调用secondResult.Add()时会抛出一个异常,因为在MyClass2上不存在这个方法。异常消息说明了这一点。
dynamic关键字也可以用于其他需要类型名的地方,例如方法参数。Add()方法可以重写为:
public int Add(dynamic var1, dynamic var2) { return var1 + var2; }
这对结果没有任何影响。在这个例子中,传送给var1和var2的值在运行期间检查,以确定加号
+
是否存在一个兼容的运算符定义。如果传送了两个int值,就存在这样的运算符。如果使用了不兼容的值,就抛出RuntimeBinderException异常。例如,如果尝试:
Console.WriteLine("firstResult call: {0}", firstResult.Add("2", 3));
异常消息就如下所示:
Cannot implicitly convert type 'string' to 'int'
从这里获得的教训是动态类型是非常强大的,但有一个警告。如果用强类型代替动态类型,就完全可以避免抛出这些异常。对于大多数自己编写的C#代码,应避免使用dynamic关键字。但是,如果需要使用它,就应使用它,并会喜欢上它---而不像过去那样可怜的程序员那样没有这个强大的工具可用。
🔚